﻿// Author: Prasanna V. Loganathar
// Project: Edi.WinDesktop
// 
// Copyright 2014 Launchark. All Rights Reserved.
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//  
// Created: 3:51 AM 15-05-2014

namespace Edi.WinDesktop.Infrastructure
{
    using System;
    using System.Diagnostics;
    using System.IO;
    using System.Reflection;
    using System.Text;
    using System.Threading;

    using Edi.Common.Infrastructure;

    using SharpLog;

    using FileAccess = Edi.Common.Infrastructure.FileAccess;

    public class FileSystem : IFileSystem
    {
        private StorageZone defaultStorageZone = StorageZone.Local;
        private string cacheFolderName = "Cache";

        private string storagePathApplication;
        private string storagePathTemp;
        private string storagePathLocal;
        private string storagePathRoaming;
        private string storagePathShared;
        private string storagePathCache;

        public string CacheFolderName
        {
            get
            {
                return cacheFolderName;
            }
            set
            {
                cacheFolderName = value;
            }
        }

        public StorageZone DefaultStorageZone
        {
            get
            {
                return defaultStorageZone;
            }
            set
            {
                if (value == StorageZone.Unspecified)
                {
                    throw new InvalidOperationException("Default storage zone cannot be unspecified.");
                }

                defaultStorageZone = value;
            }
        }

        public string ApplicationStorePath
        {
            get
            {
                LazyInitializer.EnsureInitialized(
                    ref storagePathApplication,
                    () => Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));

                return storagePathApplication;
            }

            set
            {
                storagePathApplication = value;
            }
        }

        public string LocalStorePath
        {
            get
            {
                LazyInitializer.EnsureInitialized(
                    ref storagePathLocal,
                    () =>
                    CombinePath(
                        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        GetCompanyPrefixedNamedPath()));

                return storagePathLocal;
            }

            set
            {
                storagePathLocal = value;
            }
        }

        public string TempStorePath
        {
            get
            {
                LazyInitializer.EnsureInitialized(ref storagePathTemp, Path.GetTempPath);
                return storagePathTemp;
            }

            set
            {
                storagePathTemp = value;
            }
        }

        public string RoamingStorePath
        {
            get
            {
                LazyInitializer.EnsureInitialized(
                    ref storagePathRoaming,
                    () =>
                    CombinePath(
                        Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
                        GetCompanyPrefixedNamedPath()));

                return storagePathRoaming;
            }

            set
            {
                storagePathRoaming = value;
            }
        }

        public string SharedStorePath
        {
            get
            {
                LazyInitializer.EnsureInitialized(
                    ref storagePathShared,
                    () =>
                    CombinePath(
                        Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                        GetCompanyPrefixedNamedPath()));

                return storagePathShared;
            }

            set
            {
                storagePathShared = value;
            }
        }

        public string CacheStorePath
        {
            get
            {
                LazyInitializer.EnsureInitialized(
                    ref storagePathCache,
                    () =>
                    CombinePath(
                        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        GetCompanyPrefixedNamedPath(),
                        CacheFolderName));

                return storagePathCache;
            }

            set
            {
                storagePathCache = value;
            }
        }

        public Stream CreateFile(string fileName, StorageZone zone = StorageZone.Unspecified)
        {
            return File.Create(GetPath(fileName, zone));
        }

        public void DeleteFile(string fileName, StorageZone zone = StorageZone.Unspecified)
        {
            File.Delete(GetPath(fileName, zone));
        }

        public bool FileExists(string fileName, StorageZone zone = StorageZone.Unspecified)
        {
            return File.Exists(GetPath(fileName, zone));
        }

        public bool DirectoryExists(string dirName, StorageZone zone = StorageZone.Unspecified)
        {
            return Directory.Exists(GetPath(dirName, zone));
        }

        public void CreateDirectory(string dirName, StorageZone zone = StorageZone.Unspecified)
        {
            Directory.CreateDirectory(GetPath(dirName, zone));
        }

        public void DeleteDirectory(string dirName, StorageZone zone = StorageZone.Unspecified, bool recursive = false)
        {
            Directory.Delete(GetPath(dirName, zone), recursive);
        }

        public void MoveFile(
            string srcFileName,
            string destFileName,
            StorageZone srcZone,
            StorageZone destZone = StorageZone.Unspecified)
        {
            if (srcZone == StorageZone.Unspecified)
            {
                srcZone = DefaultStorageZone;
            }

            if (destZone == StorageZone.Unspecified)
            {
                destZone = srcZone;
            }

            File.Move(GetPath(srcFileName, srcZone), GetPath(destFileName, destZone));
        }

        public void MoveDirectory(
            string srcDirName,
            StorageZone srcZone,
            string destDirName,
            StorageZone destZone = StorageZone.Unspecified)
        {
            if (srcZone == StorageZone.Unspecified)
            {
                srcZone = DefaultStorageZone;
            }

            if (destZone == StorageZone.Unspecified)
            {
                destZone = srcZone;
            }

            Directory.Move(GetPath(srcDirName, srcZone), GetPath(destDirName, destZone));
        }

        public IFileProperty GetProperties(string fileName, StorageZone zone = StorageZone.Unspecified)
        {
            throw new NotImplementedException();
        }

        public Stream GetStream(string fileName, FileAccess accessMode, StorageZone zone = StorageZone.Unspecified)
        {
            var path = GetPath(fileName, zone);

            switch (accessMode)
            {
                case FileAccess.Read:
                    return File.OpenRead(path);
                case FileAccess.Write:
                    return File.OpenWrite(path);
                case FileAccess.ReadWrite:
                    return File.Open(path, FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite);
                default:
                    return null;
            }
        }

        public virtual string GetZoneLocation(StorageZone zone)
        {
            switch (zone)
            {
                case StorageZone.Other:
                case StorageZone.Unspecified:
                    return string.Empty;
                case StorageZone.Local:
                    return LocalStorePath;
                case StorageZone.Temp:
                    return TempStorePath;
                case StorageZone.Application:
                    return ApplicationStorePath;
                case StorageZone.Cache:
                    return CacheStorePath;
                case StorageZone.Roaming:
                    return RoamingStorePath;
                case StorageZone.Shared:
                    return SharedStorePath;
                default:
                    return null;
            }
        }

        private string GetCompanyPrefixedNamedPath()
        {
            var assembly = Assembly.GetExecutingAssembly();
            var sb =
                new StringBuilder(
                    assembly.GetCustomAttribute<AssemblyCompanyAttribute>().Company + Path.DirectorySeparatorChar);
            sb.Append(assembly.GetCustomAttribute<AssemblyTitleAttribute>().Title);
            return sb.ToString();
        }

        private string CombinePath(params string[] paths)
        {
            var sb = new StringBuilder();
            foreach (var path in paths)
            {
                sb.Append(path + Path.DirectorySeparatorChar);
            }

            return sb.ToString();
        }

        private string GetPath(string fileName, StorageZone zone)
        {
            if (zone == StorageZone.Unspecified)
            {
                zone = DefaultStorageZone;
            }

            var path = Path.Combine(GetZoneLocation(zone), fileName);
            LogManager.Logger.Trace("#File " + path);

            return path;
        }
    }
}